﻿using System.Windows.Shapes;
using System.Windows;
using System;
using System.Windows.Media.Effects;
using System.Windows.Media;
using DeepEarth.Map.Core;
using DeepEarth.Core;
using System.ComponentModel;
using System.Collections.ObjectModel;

namespace DeepEarth.Toolkit.Geometry
{
    public abstract class PathBase : Path, IScalable, IMultiLocation
    {
        public static readonly DependencyProperty ScaleProperty =
            DependencyProperty.Register("Scale", typeof(double), typeof(PathBase), new PropertyMetadata(1.0, ScaleChangedHandler));

        public virtual double Scale
        {
            get
            {
                return (double)GetValue(ScaleProperty);
            }
            set
            {
                SetValue(ScaleProperty, value);
            }
        }

        public static readonly DependencyProperty GeometryProperty =
            DependencyProperty.Register("Geometry", typeof(DeepEarth.Toolkit.Geometry.Data.ShapeGeometry), typeof(PathBase), new PropertyMetadata(OnGeometryChanged));

        public DeepEarth.Toolkit.Geometry.Data.ShapeGeometry Geometry
        {
            get { return (DeepEarth.Toolkit.Geometry.Data.ShapeGeometry)GetValue(GeometryProperty); }
            set { SetValue(GeometryProperty, value); }
        }

        static void OnGeometryChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
        {
            PathBase self = (PathBase)sender;

            DeepEarth.Toolkit.Geometry.Data.ShapeGeometry oldValue = (DeepEarth.Toolkit.Geometry.Data.ShapeGeometry)args.OldValue;
            DeepEarth.Toolkit.Geometry.Data.ShapeGeometry newValue = (DeepEarth.Toolkit.Geometry.Data.ShapeGeometry)args.NewValue;

            if (oldValue != null)
            {
                oldValue.PropertyChanged -= self.OnGeometryPropertyChanged;
            }

            newValue.PropertyChanged += self.OnGeometryPropertyChanged;

            self.SyncToGeometry();

        }

        void OnGeometryPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            switch (e.PropertyName)
            {
                case "Fill":
                    this.Fill = this.Geometry.Fill;
                    break;
                case "Stroke" :
                    this.Stroke = this.Geometry.Stroke;
                    break;
                case "StrokeThickness":
                    this.StrokeThickness = this.Geometry.StrokeThickness;
                    break;
                case "StrokeDashArray":
                    this.StrokeDashArray = DeepEarth.Toolkit.Common.Converters.DoubleCollectionConverter.Convert(this.Geometry.StrokeDashArray);
                    break;
                case "Locations":
                    this.Locations = this.Geometry.Locations;
                    break;
            }
        }

        protected virtual void SyncToGeometry()
        {
            this.Stroke = this.Geometry.Stroke;
            this.Fill = this.Geometry.Fill;
            this.StrokeThickness = this.Geometry.StrokeThickness;
            this.StrokeDashArray = DeepEarth.Toolkit.Common.Converters.DoubleCollectionConverter.Convert(this.Geometry.StrokeDashArray);
            this.Locations = this.Geometry.Locations;
        }

        public static readonly DependencyProperty LocationsProperty =
           DependencyProperty.Register("Locations", typeof(ObservableCollection<Location>), typeof(PathBase), new PropertyMetadata(LocationsPropertyChangedHandler));

        public ObservableCollection<Location> Locations
        {
            get { return (ObservableCollection<Location>)GetValue(LocationsProperty); }
            set { SetValue(LocationsProperty, value); }
        }

        public static void LocationsPropertyChangedHandler(DependencyObject sender, DependencyPropertyChangedEventArgs args)
        {
            PathBase self = (PathBase)sender;

            if (args.OldValue != null)
            {
                ObservableCollection<Location> oldLocations = args.OldValue as ObservableCollection<Location>;
                oldLocations.CollectionChanged -= self.locations_CollectionChanged;
            }

            if (args.NewValue != null)
            {
                ObservableCollection<Location> newLocations = args.NewValue as ObservableCollection<Location>;

                self.LocationPropertyChanged();

                newLocations.CollectionChanged += self.locations_CollectionChanged;

                self.locations_CollectionChanged(null, null);
            }
        }

        void locations_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
        {
            BoundingRectangle = new LocationRectangle(this.Locations);
            Refresh();
        }

        protected virtual void LocationPropertyChanged()
        {        
        }

        protected LocationRectangle BoundingRectangle { get; set; }

        protected Point Offset { get; set; }
        protected double MaxBound { get; set; }

        public void Refresh(Point offset, double maxBound)
        {
            Offset = offset;
            MaxBound = maxBound;

            Refresh();
        }

        protected abstract void Refresh();

        void Translate(Point delta, Location locationDelta)
        {
            if (delta.X == 0 && delta.Y == 0)
                return;

            foreach (Location l in Locations)
            {
                Location newLoc;

                if (PreserveShape)
                {
                    Point p = this.Map.LocationToViewportPoint(l);
                    Point adjusted = new Point
                    {
                        X = p.X + delta.X,
                        Y = p.Y + delta.Y
                    };

                    newLoc = this.Map.ViewportPointToLocation(adjusted);
                }
                else
                {
                    newLoc = new Location
                    {
                        Longitude = l.Longitude + locationDelta.Longitude,
                        Latitude = l.Latitude + locationDelta.Latitude
                    };
                }

                l.Longitude = newLoc.Longitude;
                l.Latitude = newLoc.Latitude;

                if (l.Longitude > 180)
                {
                    l.Longitude -= 360;
                }
                else if (l.Longitude < -180)
                {
                    l.Longitude += 360;
                }

                if (l.Latitude > 90)
                {
                    l.Latitude -= 180;
                }
                else if (l.Latitude < -90)
                {
                    l.Latitude += 180;
                }
            }

            BoundingRectangle = new LocationRectangle(this.Locations);
            Refresh();
        }

        public virtual bool MaintainConstantSize { get; set; }

        private static void ScaleChangedHandler(DependencyObject sender, DependencyPropertyChangedEventArgs args)
        {
            if (args.NewValue != null)
            {
                PathBase pb = sender as PathBase;

                double newValue = (double)args.NewValue;

                if (!pb.MaintainConstantSize)
                {
                    pb.ApplyScale(newValue, 1.0);
                }
            }
        }

        public virtual void ApplyScale(double scale, double appliedScaleTransform)
        {            
        }

        public static readonly DependencyProperty IsSelectedProperty =
            DependencyProperty.Register("IsSelected", typeof(bool), typeof(PathBase), new PropertyMetadata(IsSelectedChanged));

        public bool IsSelected
        {
            get { return (bool)GetValue(IsSelectedProperty); }
            set { SetValue(IsSelectedProperty, value); }
        }

        private static void IsSelectedChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
        {
            PathBase pb = sender as PathBase;

            bool newValue = (bool)args.NewValue;

#if !WINDOWS_PHONE
            if (newValue)
            {
                pb.Effect = pb.SelectedEffect;
            }
            else
            {
                pb.Effect = null;
            }
#endif

            // 1. Child classes can just override this method for efficiency
            pb.SelectedChangedOverride();

            if (pb.IsSelectedChangedHandler != null)
            {
                pb.IsSelectedChangedHandler(newValue);
            }
        }

        protected virtual void SelectedChangedOverride()
        {
        }

        public static readonly DependencyProperty IsSelectedChangedHandlerProperty = DependencyProperty.Register(
            "IsSelectedChangedHandler",
            typeof(Action<bool>),
            typeof(PathBase),
            null);

        public Action<bool> IsSelectedChangedHandler
        {
            get { return (Action<bool>)GetValue(IsSelectedChangedHandlerProperty); }
            set
            {
                SetValue(IsSelectedChangedHandlerProperty, value);
            }
        }

#if !WINDOWS_PHONE
        public static readonly DependencyProperty SelectedEffectProperty =
    DependencyProperty.Register("SelectedEffect", typeof(Effect), typeof(PointBase),
                    null);

        public Effect SelectedEffect
        {
            get { return (Effect)GetValue(SelectedEffectProperty); }
            set { SetValue(SelectedEffectProperty, value); }
        }
#endif

        public static readonly DependencyProperty PreserveShapeProperty =
            DependencyProperty.Register("PreserveShape", typeof(bool), typeof(PathBase), new PropertyMetadata(false));

        public bool PreserveShape
        {
            get { return (bool)GetValue(PreserveShapeProperty); }
            set { SetValue(PreserveShapeProperty, value); }
        }

        private IMap map;
        protected IMap Map
        {
            get
            {
                if (map == null)
                {
                    map = DeepEarth.Map.Core.Utilities.VisualTree.FindAncestor<IMap>(this);
                }

                return map;
            }
        }

        // HACK - Can Rx be used to deprecate these variables?
        private bool isDragging = false;
        Point lastDragPoint;
        Location lastDragLocation;
        bool hasDragged = false;

        private bool enableDrag;
        public bool EnableDrag
        {
            get
            {
                return enableDrag;
            }
            set
            {
                enableDrag = value;
            }
        }

        void InitializeInteractionHandlers()
        {
            this.MouseLeftButtonDown += (o, e) =>
            {
                if (enableDrag)
                {
                    isDragging = true;
                    lastDragPoint = e.GetPosition(Map.AsFrameworkElement);
                    lastDragLocation = this.Map.ViewportPointToLocation(lastDragPoint);
                    this.CaptureMouse();
                    //
                    //e.Handled = true;
                    hasDragged = false;
                }

                e.Handled = EnableDrag;
            };

            this.MouseLeftButtonUp += (o, e) =>
            {
                isDragging = false;
                this.ReleaseMouseCapture();

                if (!hasDragged)
                {
                    this.IsSelected = !IsSelected;
                }
            };

            this.MouseMove += (o, e) =>
            {
                if (isDragging)
                {
                    hasDragged = true;

                    Point currentPosition = e.GetPosition(Map.AsFrameworkElement);
                    Location currentLocation = this.Map.ViewportPointToLocation(currentPosition);
                    Point delta = new Point(currentPosition.X - lastDragPoint.X, currentPosition.Y - lastDragPoint.Y);
                    Location locationDelta = new Location
                    {
                        Longitude = currentLocation.Longitude - lastDragLocation.Longitude,
                        Latitude = currentLocation.Latitude - lastDragLocation.Latitude
                    };
                    lastDragPoint = currentPosition;
                    lastDragLocation = currentLocation;
                    Translate(delta, locationDelta);
                }
            };
        }

        public PathBase()
        {
            this.Locations = new ObservableCollection<Location>();

            this.Stroke = new SolidColorBrush(Colors.Black);
            this.StrokeThickness = 1;

            InitializeInteractionHandlers();

            //Default locationToPoint
            this.LocationToPoint = (location) =>
                {
                    if (Map != null)
                    {
                        return Map.LocationToViewportPoint(location);
                    }
                    else
                    {
                        return new Point(0, 0);
                    }
                };
        }

        // Function to convert a location to a pixel.
        public Func<Location, Point> LocationToPoint { get; set; }

        #region IMultiLocation

        System.Collections.Generic.IEnumerable<Location> IMultiLocation.Locations
        {
            get { return Locations; }
        }

        public Location Position
        {
            get
            {
                return BoundingRectangle.Center;
            }
            set
            {
                throw new NotImplementedException();
            }
        }

        public LocationRectangle BoundingBox
        {
            get { return BoundingRectangle; }
        }

        #endregion

    }
}
